(function (win) {
  var
    each = function (o, fn, l, i) {
      if (isArr(o)) { for (i = i || 0, l = l ? min(i + l, o.length) : o.length; i < l; i++) { fn(i, o[i]); } } //if []
      else { for (i in o) { o.hasOwnProperty(i) && fn(i, o[i]); } } //if {}
    },
    setORP = function (o, p, v, e) {
      if (isStr(p)) { return Object.defineProperty(o, p, { configurable: false, enumerable: (isVoid(e) ? true : !!e), writable: false, value: v }); }
      var en = isVoid(v) ? true : !!v, po = {};
      each(p, function (k, v) { po[k] = { configurable: false, enumerable: en, writable: false, value: v }; });
      Object.defineProperties(o, po);
    };

  $.fn.extend({
    dis: function () { this.prop('disabled', true) },
    ena: function () { this.prop('disabled', false) },
    formClear: function () { $.each(this.find('[id]'), function (i, it) { it.value = ''; }); },
    formJSON: function () {
      const ret = {};
      $.each(this.find('[name]'), function (i, it) {
        ret[it.name] = it.value;
      });
      return ret;
    },
    formJSONS: function () { return JSON.stringify(this.formJSON()); },
    jsonForm: function (o) {
      for (let k in o) {
        if (o.hasOwnProperty(k)) {
          let it = this.find(`[name=${k}]`);
          (it.length > 0) && (it.val(o[k]));
        }
      }
      return this;
    },
    jsonsForm: function (s) { return jsonForm(JSON.parse(s)); }
  });

  let uuidKey = 0, uuidCount = 0;
  const defaultUuidPrefix = 'u', uuid = function (customUuidPrefix) {
    const now = (new Date()).getTime(), uuidPrefix = customUuidPrefix || defaultUuidPrefix;
    now === uuidKey ? (uuidCount++) : (uuidKey = now);
    return `${uuidPrefix}${now}${defaultUuidPrefix}${uuidCount}`;
  };

  const
    typeOf = function (o) { return Object.prototype.toString.call(o).slice(8, -1).toLowerCase(); },
    isVoid = function (o) { return typeof o == 'undefined'; },
    isStr = function (o) { return typeOf(o) == 'string'; },
    isNum = function (o) { return typeOf(o) == 'number'; },
    isFn = function (o) { return typeof o == 'function'; },
    isArr = Array.isArray,
    is0Arr = function (o) { return o == !1; },
    is0Obj = function (o) {  // or is0Obj = $.isEmptyObject,
      //[ref]https://juejin.cn/post/6910778817485897736
      return o && o.constructor === Object && Object.keys(o) == !1;
    },
    toJSONS = function (o, filter, spec) { var fn = isFn(filter) ? filter : null, s = spec || (isNum(filter) ? filter : null); return JSON.stringify(o, fn, s); },
    //fnGet = function (act, ok, ng, ov) { $.get(act).done(ok).fail(ng || fnAjaxComFail).always(ov || fnAjaxComAlways); },
    // fnAjaxComOK4Com = fnAjaxComOK4Post - [textStatus, jqXHR] = function (retJSON) {...}
    // fnAjaxComOK4Post = function (retJSON, textStatus, jqXHR) {...}
    // fnAjaxComOK4Get = function (retJSON) {...}
    fnAjaxComFail = function (jqXHR, textStatus, errMsg) { $.log('XHR error: ' + errMsg); },  //textStatus = error
    fnAjaxComAlways = function (deferred, textStatus, jqXHR) { $.log('XHR final: ' + textStatus); };

  $.ajaxSetup({ traditional: true });
  $.extend({
    isStr, isFn, isArr, is0Arr, is0Obj, toJSONS, uuid,
    xhr: function (o) {  //o = { act, d, ok, ng, ov }
      $.post(o.act, o.d).done(function (retJSON, textStatus, jqXHR) { o.ok(retJSON) })
        .fail(o.ng || fnAjaxComFail).always(o.ov || fnAjaxComAlways);
    },
    TQ: function (o) { return (o ? TQ.opt(o) : TQ.start()); },
    log: msg => { console.log(msg); }
  });

  setORP($.TQ, {
    clear: function () { tq = { stateFn: 0, sleep: 50, optSleep: 0, arrSleep: [], arrTask: [], curTask: 0, curRet: 0, isStop: 0, states: [], globaOpt: {}, overFn: 0, isOptTaskArr: 0 }; return TQ; },
    opt: function (o) { //o.sleep, o.opt, o.state, o.over, o.tq
      TQ.clear();
      o.state && (tq.stateFn = o.state);
      o.over && (tq.overFn = o.over);
      o.sleep && (tq.optSleep = o.sleep);
      o.tq && (TQ.add(o.tq)) && (tq.isOptTaskArr = $.isArr(o.tq));
      if (o.opt) { for (var p in o.opt) { if (o.opt.hasOwnProperty(p)) { tq.globaOpt[p] = o.opt[p]; } } }
      return TQ;
    },
    add: function (o) { for (var i = 0, a = toArr(o); i < a.length; i++) { tq.arrTask.push($.isFn(a[i]) ? { do: a[i] } : a[i]) } return TQ; },
    start: function (fn) {
      (fn = function (to) {
        if (tq.arrTask.length < 1) return TQ.clear();
        if (tq.isStop) return TQ;
        to = tq.curTask = tq.arrTask.shift();
        if (!to.hasShowState) {
          to.hasShowState = 1;
          if (tq.stateFn && (tq.isOptTaskArr || !$.isNope(to.state))) { tq.stateFn(to.state || '', tq.curRet, tq.curTask.opt, tq.globaOpt); tq.arrTask.unshift(to); return setTimeout(fn, 20); }
        }
        if (!to.hasDone) {
          if (to.do) {
            try {
              tq.curRet = to.do(tq.curRet, tq.curTask.opt, tq.globaOpt);
            } finally { to.over && (to.over()); tq.overFn && tq.overFn(tq.curRet, tq.curTask.opt, tq.globaOpt); }
          }
          to.hasDone = 1;
        }
        to.clearIsGoon && (to.clearIsGoon = to.isGoon = 0);
        !to.goon && (to.isGoon = 1);
        if (!to.isGoon) {
          try {
            to.isGoon = to.goon(tq.curRet, tq.curTask.opt, tq.globaOpt);
          } finally { to.over && (to.over()); tq.overFn && tq.overFn(tq.curRet, tq.curTask.opt, tq.globaOpt); }
          !to.isGoon && (TQ.redo(1));
        }
        to.isGoon && tq.stateFn && !$.isNope(to.overState) && (tq.stateFn(to.overState, tq.curRet, tq.curTask.opt, tq.globaOpt));
        setTimeout(fn, (tq.arrSleep.pop() || tq.optSleep || tq.sleep));
      })();
      return TQ;
    },
    stop: function () { tq.isStop = 1; return TQ; },
    redo: function (isGoonOnly) { if (!isGoonOnly) { tq.curTask.clearIsGoon = 1; tq.curTask.hasDone = 0; } tq.arrTask.unshift(tq.curTask); return TQ; },
    sleep: function (o) { tq.arrSleep.push(o); return TQ; },
    size: function () { return tq.arrTask.length; }
  });

  // UI
  var $$$ = {};
  $(function () {
    $.each($('[id]'), function (i, v) { $$$[v.id] = $('#' + v.id); });
  });
  win.$$$ = $$$;
})(window);